﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ShellContextMenu.cs" company="TechBits">
//   Copyright (c) TechBits. All rights reserved.
// </copyright>
// <author>
//   Leandro G Vacirca.
// </author>
// <summary>
//   Defines the ShellContextMenu type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace IntelliShortCut.Core
{
    using System;
    using System.IO;
    using System.Security.Permissions;

    using Microsoft.Win32;
    
    /// <summary>
    /// Enables to add a windows explorer context menu associated to an application.
    /// </summary>
    public class ShellContextMenu
    {      
        #region "Private Members"

        /// <summary>
        /// Saves the context menu data.
        /// </summary>
        private ContextMenuData _contextMenuData;

        #endregion

        #region "Constructor"

        /// <summary>
        /// Initializes a new instance of the <see cref="ShellContextMenu"/> class. 
        /// </summary>
        /// <param name="classRootTypePath">The class root type.</param>
        /// <param name="contextMenuName">The context menu name.</param>
        /// <param name="appPath">The app path.</param>
        /// <param name="appArgs">The app args delimited by spaces.</param>
        public ShellContextMenu(string classRootTypePath, string contextMenuName, string appPath, string appArgs)
        {
            if (string.IsNullOrEmpty(classRootTypePath) || string.IsNullOrEmpty(contextMenuName) || string.IsNullOrEmpty(appPath))
            {
                throw new ApplicationException("The ContextMenu class root type, the name or the application path could not be empty.");
            }

            _contextMenuData = new ContextMenuData { ClassRootTypePath = classRootTypePath, Name = contextMenuName, Path = appPath, Args = appArgs };
            CheckSecurity();
        }

        #endregion

        #region "Public Methods"

        /// <summary>
        /// Installs the command context menu.
        /// </summary>
        public void Install()
        {
            var cmdValue = string.Format("{0} \"%1\" \"{1}\"", _contextMenuData.Path, _contextMenuData.Args);
            AddCommandContextMenu(cmdValue);
        }

        /// <summary>
        /// Uninstalls the command context menu.
        /// </summary>
        public void Uninstall()
        {
            RemoveCommandContextMenu();
        }

        #endregion

        #region "Private Methods"

        /// <summary>
        /// Checks the access permissions to the windows registry.
        /// </summary>
        private void CheckSecurity()
        {
            var pathList = string.Concat("HKEY_CLASSES_ROOT\\", Path.Combine(_contextMenuData.ClassRootTypePath, _contextMenuData.Name));
            var regPerm = new RegistryPermission(RegistryPermissionAccess.Write, pathList);
            pathList = Path.Combine(pathList, "command");
            regPerm.AddPathList(RegistryPermissionAccess.Write, pathList);

            // Throw an exception if permissions are not guarranteed
            regPerm.Demand();
        }

        /// <summary>
        /// Creates the command context menu.
        /// </summary>
        /// <param name="commandValue">Value of the context command menu.</param>
        private void AddCommandContextMenu(string commandValue)
        {
            RegistryKey regMenu = null;
            RegistryKey regCmd = null;

            try
            {
                var subkey = Path.Combine(_contextMenuData.ClassRootTypePath, _contextMenuData.Name);
                regMenu = Registry.ClassesRoot.CreateSubKey(subkey);
                if (regMenu != null)
                {
                    regMenu.SetValue(string.Empty, _contextMenuData.Name);
                }

                subkey = Path.Combine(subkey, "command");
                regCmd = Registry.ClassesRoot.CreateSubKey(subkey);
                if (regCmd != null)
                {
                    regCmd.SetValue(string.Empty, commandValue);
                }
            }
            finally
            {
                if (regMenu != null)
                {
                    regMenu.Close();
                }

                if (regCmd != null)
                {
                    regCmd.Close();
                }
            }
        }

        /// <summary>
        /// Removes the context command menu.
        /// </summary>
        private void RemoveCommandContextMenu()
        {
            var subkey = Path.Combine(_contextMenuData.ClassRootTypePath, _contextMenuData.Name);
            var reg = Registry.ClassesRoot.OpenSubKey(Path.Combine(subkey, "command"));
            if (reg != null)
            {
                reg.Close();
                Registry.ClassesRoot.DeleteSubKey(subkey);
            }

            reg = Registry.ClassesRoot.OpenSubKey(subkey);
            if (reg == null)
            {
                return;
            }

            reg.Close();
            Registry.ClassesRoot.DeleteSubKey(subkey);
        }

        #endregion

        #region "Structs"

        /// <summary>
        /// Context menu data struct.
        /// </summary>
        private struct ContextMenuData
        {
            /// <summary>
            /// Gets or sets the Class Root Type
            /// </summary>
            /// <value>The class root type.</value>
            public string ClassRootTypePath { get; set; }

            /// <summary>
            /// Gets or sets Name.
            /// </summary>
            /// <value>The name of the context menu.</value>
            public string Name { get; set; }

            /// <summary>
            /// Gets or sets Path.
            /// </summary>
            /// <value>The path to exe file associated to the context menu command.</value>
            public string Path { get; set; }

            /// <summary>
            /// Gets or sets Args.
            /// </summary>
            /// <value>The optional string that will be passed to the application.</value>
            public string Args { get; set; }
        }

        #endregion
    }
}